/////////////////////////////////////////////////////////////////////////////
// Name:        ax_speech.h
// Purpose:     Speech class
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#ifndef _AX_SPEECH_H_
#define _AX_SPEECH_H_

/**
    @class AxSpeech

    @category{ax}
*/

/*
 * Includes
 */

#include "wx/event.h"
#include "wx/dynarray.h"

#include "tts/tts.h"
#include "ax/ax_menu.h"

class AxSpeechEvent;
class AxSpeechEventHandler;
class AxMenu;
class AxMenuItem;
class wxConfigBase;
class AxSpeechEventArray;

enum {
    AxSpeechVerbosityLowest=1,
    AxSpeechVerbosityLow=2,
    AxSpeechVerbosityMedium=3,
    AxSpeechVerbosityHigh=4,
    AxSpeechVerbosityHighest=5
};

typedef enum {
    AxSpeechMainChannel=    0,
    AxSpeechContentChannel= 1,

    AxSpeechDefaultChannel= -1,
    AxSpeechAllChannels=    -2
};

typedef int AxSpeechChannel;

/**
    @class AxSpeechPolicy

    A class holding policy information about how to speak
    elements of the interface.

    @category{ax}
 */

class AxSpeechPolicy: public wxObject
{
public:
    DECLARE_DYNAMIC_CLASS( AxSpeechPolicy )

    AxSpeechPolicy() { Init(); }
    AxSpeechPolicy(const AxSpeechPolicy& policy) { Copy(policy); }

    void Init();

    void Copy(const AxSpeechPolicy& policy);
    void operator=(const AxSpeechPolicy& policy) { Copy(policy); }

    /// Set verbosity level (1 - 5)
    void SetVerbosityLevel(int level) { m_verbosityLevel = level; }

    /// Set verbosity level (1 - 5)
    int GetVerbosityLevel() const { return m_verbosityLevel; }

    /// Returns true if verbosity is at least @a level
    bool VerbosityAtLeast(int level) const { return m_verbosityLevel >= level; }

    /// Enables or disables speech
    void EnableSpeech(bool enabled = true) { m_enableSpeech = enabled; }

    /// Returns true if speech is enabled
    bool IsSpeechEnabled() const { return m_enableSpeech; }

    /// Enables or disables multiple simultaneous voices.
    void EnableMultipleVoiceMode(bool enabled = true) { m_enableMultipleVoiceMode = enabled; }

    /// Returns true if speech is enabled
    bool IsMultipleVoiceModeEnabled() const { return m_enableMultipleVoiceMode; }

public:

    bool    m_enableSpeech;
    bool    m_enableMultipleVoiceMode;
    int     m_verbosityLevel;
};

enum { 
    // Purge previous speech events
    AxSpeechFlagsPurge=0x0001,

    // Purge previous speech events on all channels
    AxSpeechFlagsPurgeAll=0x0002,

    // Don't allow this event to be purged
    AxSpeechFlagsInviolate=0x0004,

    // Start speaking immediately instead of waiting until system is idle
    AxSpeechFlagsImmediate=0x0008,

    // Use content channel instead of main channel
    AxSpeechFlagsContentChannel=0x0010
};

/**
    @class AxSpeechEvent

    A class holding information about a speech event.
    Currently these events are not processed in the same
    way as wxWidgets events, but we derive from wxNotifyEvent
    in case we allow convention event processing in the future.

    @category{ax}
 */

class AxSpeechEvent: public wxNotifyEvent
{
public:
    DECLARE_DYNAMIC_CLASS( AxSpeechEvent )

    AxSpeechEvent() { Init(); }

    AxSpeechEvent(const wxString& eventType, const wxString& text = wxEmptyString,
                    int verbosityLevel = 0)
    { Init(); m_speechEventType = eventType; m_text = text; m_verbosityLevel = verbosityLevel; }
    AxSpeechEvent(const AxSpeechEvent& event) { Init(); Copy(event); }

    void Init();

    void Copy(const AxSpeechEvent& event);

    virtual wxEvent* Clone() const { return new AxSpeechEvent(*this); }

    void SetSpeechEventType(const wxString& eventType) { m_speechEventType = eventType; }
    const wxString& GetSpeechEventType() const { return m_speechEventType; }

    void SetText(const wxString& text) { m_text = text; }
    const wxString& GetText() const { return m_text; }

    /// Set the verbosity of the event. This will be compared with
    /// the current verbosity level in AxSpeech, to see whether it's
    /// applicable. The lower the verbosity level in the event,
    /// the more chance it has of being spoken. The default is zero,
    /// which means the event will not be ruled out immediately but will
    /// be considered on its own merits inside the speech event handler.
    void SetVerbosityLevel(int level) { m_verbosityLevel = level; }
    int GetVerbosityLevel() const { return m_verbosityLevel; }

    /// Sets the current verbosity policy. By default, this is -1
    /// and will be set to the current global setting. This can
    /// be set to a specific value in order to specify the level
    /// of detail (for example describing the current context
    /// in lesser or greater detail).
    void SetVerbosityPolicy(int level) { m_verbosityPolicy = level; }
    int GetVerbosityPolicy() const { return m_verbosityPolicy; }

    void SetNavigationHint(AxNavigationHint hint) { m_navigationHint = hint; }
    int GetNavigationHint() const { return m_navigationHint; }

    void PurgeSpeech() { m_flags |= AxSpeechFlagsPurge; }
    void PurgeAllSpeech() { m_flags |= AxSpeechFlagsPurgeAll; }
    bool GetPurgeSpeech() const { return (m_flags & AxSpeechFlagsPurge) == AxSpeechFlagsPurge; }
    bool GetPurgeAllSpeech() const { return (m_flags & AxSpeechFlagsPurgeAll) == AxSpeechFlagsPurgeAll; }

    void SetFlags(int flags) { m_flags = flags; }
    int GetFlags() const { return m_flags; }

    void SetMenu(AxMenu* menu) { m_menu = menu; }
    AxMenu* GetMenu() const { return m_menu; }
    
    void SetMenuItem(AxMenuItem* item) { m_menuItem = item; }
    AxMenuItem* GetMenuItem() const { return m_menuItem; }

    AxSpeechChannel GetChannel() const { return m_flags & AxSpeechFlagsContentChannel ? AxSpeechContentChannel : AxSpeechMainChannel; }

protected:
    wxString            m_speechEventType;
    wxString            m_text; // General text to emit
    int                 m_verbosityLevel; // verbosity of current event
    int                 m_verbosityPolicy; // overrides current policy
    int                 m_flags;
    AxMenu*             m_menu;
    AxMenuItem*         m_menuItem;
    AxNavigationHint    m_navigationHint;
};

WX_DEFINE_ARRAY_PTR(AxSpeechEvent*, AxSpeechEventArray);

class AxSpeechInfo
{
public:
    AxSpeechInfo();

    wxTextToSpeech          m_textToSpeech;
    wxTTSSpeechSettingsInfo m_speechSettingsInfo;
    wxString                m_currentText;
    int                     m_freezeCount;
    int                     m_speechCounter;
    AxSpeechEventArray      m_speechEvents;
};

/**
    @class AxSpeech

    Route AxTk speech events through objects of this class.

    @category{ax}
 */

#define AxSpeechEventActivateItem       wxT("ACTIVATEITEM")
#define AxSpeechEventRefreshMenu        wxT("REFRESHMENU")
#define AxSpeechEventChangeSelection    wxT("SELECTIONCHANGED")
#define AxSpeechEventToggleSelection    wxT("TOGGLESELECTION")
#define AxSpeechEventChangeValue        wxT("CHANGEVALUE")
#define AxSpeechEventItemLabelChanged   wxT("ITEMLABELCHANGED")
#define AxSpeechEventTaskAdded          wxT("TASKADDED")
#define AxSpeechEventTaskDeleted        wxT("TASKDELETED")
#define AxSpeechEventTaskChanged        wxT("TASKCHANGED")
#define AxSpeechEventText               wxT("TEXT")
#define AxSpeechEventInitialView        wxT("INITIALVIEW")
#define AxSpeechEventDescribeContext    wxT("DESCRIBECONTEXT")
#define AxSpeechEventDescribeItem       wxT("DESCRIBEITEM")

class AxSpeech: public wxEvtHandler
{
public:
    DECLARE_CLASS( AxSpeech )

public:
// Constructors

    AxSpeech();
    ~AxSpeech();

// Operations

    void Init();

    /// Say some text
    bool Say(const wxString& text, int flags = AxSpeechFlagsPurge, int verbosity = 2);

    /// Schedules a speech event, to be processed in idle time.
    bool EmitSpeech(AxSpeechEvent& event);

    /// Processes the event causing immediate speech. This function
    /// is called in idle time.
    virtual bool ProcessSpeechEvent(AxSpeechEvent& event);

    /// Stop speaking, first in the main channel. Call again if the
    /// content is also speaking.
    virtual bool Stop();

    /// Stop speaking in the specified channel.
    virtual bool Stop(AxSpeechChannel channel);

    /// Pause or resume speaking on relevant channel.
    virtual bool PauseOrResume();

    /// Pause or resume speaking on the specified channel.
    virtual bool PauseOrResume(AxSpeechChannel channel);

    /// Returns the main wxTextToSpeech object.
    wxTextToSpeech& GetTextToSpeech(AxSpeechChannel channel = AxSpeechMainChannel, bool normalize = true) { return GetChannelInfo(channel, normalize).m_textToSpeech; }
    const wxTextToSpeech& GetTextToSpeech(AxSpeechChannel channel = AxSpeechMainChannel, bool normalize = true) const { return GetChannelInfo(channel, normalize).m_textToSpeech; }

    /// Sets the speech settings info for the main channel.
    void SetSpeechSettingsInfo(const wxTTSSpeechSettingsInfo& info, AxSpeechChannel channel = AxSpeechMainChannel, bool normalize = true) { GetChannelInfo(channel, normalize).m_speechSettingsInfo = info; }

    /// Gets the speech settings info for the main channel.
    const wxTTSSpeechSettingsInfo& GetSpeechSettingsInfo(AxSpeechChannel channel = AxSpeechMainChannel, bool normalize = true) const { return GetChannelInfo(channel, normalize).m_speechSettingsInfo; }

    /// Gets the speech settings info for the main channel.
    wxTTSSpeechSettingsInfo& GetSpeechSettingsInfo(AxSpeechChannel channel = AxSpeechMainChannel, bool normalize = true) { return GetChannelInfo(channel, normalize).m_speechSettingsInfo; }

    /// Get the speech info for the given channel. If normalize is true, the multiple voices policy setting will
    /// be taken into account and may change the channel.
    const AxSpeechInfo& GetChannelInfo(AxSpeechChannel channel, bool normalize = true) const;

    /// Get the speech info for the given channel
    AxSpeechInfo& GetChannelInfo(AxSpeechChannel channel, bool normalize = true);

    /// Normalize the channel, i.e. convert it to a usable channel taking the multiple voice mode setting into account.
    AxSpeechChannel NormalizeChannel(AxSpeechChannel channel) const;

    /// Sets the transformer. You may want to create a custom transformer.
    void SetTransformer(wxTTSTransformer* transformer);

    /// Gets the transformer.
    wxTTSTransformer* GetTransformer() const { return m_transformer; }

    /// Sets the policy
    void SetPolicy(const AxSpeechPolicy& policy) { m_policy = policy; }

    /// Returns the policy
    AxSpeechPolicy& GetPolicy() { return m_policy; }

    /// Returns the policy
    const AxSpeechPolicy& GetPolicy() const { return m_policy; }

    /// Returns the text about to be, or being, spoken, for the main channel.
    const wxString& GetCurrentText() const { return m_mainSpeechChannel.m_currentText; }

    /// Returns the text about to be, or being, spoken, for the content channel.
    const wxString& GetCurrentContentText() const { return m_contentSpeechChannel.m_currentText; }

    /// Sets the text to be spoken for the main channel. Usually you will use EmitSpeech
    /// and not this function.
    void SetCurrentText(const wxString& text, AxSpeechChannel channel = AxSpeechMainChannel) { GetChannelInfo(channel).m_currentText = text; }

    /// Returns true if frozen, that is, awaiting Thaw call before speaking.
    bool IsFrozen(AxSpeechChannel channel = AxSpeechMainChannel) const;

    /// Freeze - don't speak between Freeze/Thaw calls.
    bool Freeze(AxSpeechChannel channel = AxSpeechMainChannel);

    /// Thaw - speak if freeze count is zero.
    bool Thaw(AxSpeechChannel channel = AxSpeechMainChannel);

    /// A way to determine whether speech has been output since the
    /// last counter was examined.
    int GetSpeechCounter(AxSpeechChannel channel = AxSpeechMainChannel) const { return GetChannelInfo(channel).m_speechCounter; }

    /// Speak text if not frozen. Otherwise, append to stored
    /// text awaiting a Thaw call.
    bool Speak(const wxString& text, int speechChannel = AxSpeechMainChannel);

    /// Pushes a handler on the front of the speech event handler stack
    bool Push(AxSpeechEventHandler* handler);

    /// Pushes a handler on the end of the speech event handler stack
    bool PushEnd(AxSpeechEventHandler* handler);

    /// Pops a handler from the front of the speech event handler, and deletes it
    bool Pop();

    /// Initialise standard speech engine handlers
    virtual bool InitialiseStandardSpeechHandlers();

    /// Load speech settings from config
    virtual bool LoadSpeechSettings(wxConfigBase& config);

    /// Save speech settings to config
    virtual bool SaveSpeechSettings(wxConfigBase& config);

    /// Are the currently selected voices compatible?
    virtual bool AreVoicesCompatible() const;

    /// Call this after a change in multiple voice mode
    void SetupMultipleVoices();

    /// Get pending speech events
    AxSpeechEventArray& GetPendingSpeechEvents(AxSpeechChannel channel = AxSpeechMainChannel) { return GetChannelInfo(channel).m_speechEvents; }

    /// Add to pending speech events
    void ScheduleSpeechEvent(AxSpeechEvent& event);

    /// Clear pending speech events
    void ClearSpeechEvents(AxSpeechChannel channel = AxSpeechMainChannel);

    /// Purge pending speech events
    void PurgeSpeechEvents(AxSpeechChannel channel = AxSpeechMainChannel);

    /// Process any pending speech events
    void ProcessPendingSpeechEvents();

protected:
    AxSpeechInfo            m_mainSpeechChannel;
    AxSpeechInfo            m_contentSpeechChannel;

    wxTTSTransformer*       m_transformer;
    AxSpeechPolicy          m_policy;
    AxSpeechEventHandler*   m_handler;
};

/**
    @class AxSpeechEventHandler

    An object that handles speech events.

    @category{ax}
 */

class AxSpeechEventHandler: public wxEvtHandler
{
public:
    DECLARE_CLASS( AxSpeechEventHandler )

    AxSpeechEventHandler() { m_nextHandler = NULL; }

    virtual bool ProcessSpeechEvent(AxSpeech* speech, AxSpeechEvent& event) = 0;

    AxSpeechEventHandler* GetNextHandler() const { return m_nextHandler; }
    void SetNextHandler(AxSpeechEventHandler* handler) { m_nextHandler = handler; }

    /// Is this punctuation?
    static bool IsPunctuation(const wxChar& ch);

    /// Remove trailing punctuation
    static bool RemoveTrailingPunctuation(wxString& str);

    /// Add a full stop to the string, unless there's already one there.
    /// Remove extra punctuation, e.g. semicolon.
    static bool AddStop(wxString& str, const wxString& punct = wxT("."));

    /// Add a semicolon to the string to create a pause, unless there's already one there.
    /// Remove extra punctuation.
    static bool AddPause(wxString& str, const wxString& punct = wxT(";"));

protected:

    AxSpeechEventHandler*   m_nextHandler;
};

/**
    @class AxStandardSpeechEventHandler

    The standard handler for speech events.

    @category{ax}
 */

class AxStandardSpeechEventHandler: public AxSpeechEventHandler
{
public:
    DECLARE_DYNAMIC_CLASS( AxStandardSpeechEventHandler )

    AxStandardSpeechEventHandler() {}

    virtual bool ProcessSpeechEvent(AxSpeech* speech, AxSpeechEvent& event);

protected:

};


#endif
    // _AX_SPEECH_H_
